/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: property_impl.cpp
 *
 * Purpose: property inner implementation for base library
 *
 * Developer:
 *   wen.gu , 2019-10-09
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "panda/sys/internal/property_impl.h"


#include <chrono>
#include <thread>     

#include "panda/sys/serializer.h"
 #include "panda/core/utils.h"

#define LOG_TAG "prip"
#include "panda/core/log.h"

namespace panda
{
namespace sys
{
namespace internal_impl
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define PROP_IMPL_GETOR(type) \
type PropertyImpl::getOr(const std::string& key, type  defVal){ \
    type val = 0;                                               \
    if (get(key, val)){                                         \
        return val;                                             \
    }                                                           \
    return defVal;                                              \
}
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/



/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/


 //static 
PropertyImpl& PropertyImpl::instance()
{
    static PropertyImpl mImpl;

    return mImpl;
}

PropertyImpl::~PropertyImpl()
{
    /** todo something */
}

PropertyImpl::PropertyImpl()
{
    /** default, initialize as read only mode, 
     * mean that current process is not the owner 
     * of  property shared memory area.
     */
    initialize(false);
    /** todo something */
}


bool PropertyImpl::initialize(bool isOwner/* = false*/)
{
    bool ret = true;

    if (!mIsInitialized)
    {   
        SystemProperty& sysProp = SystemProperty::instance();
        if (!sysProp.isInited())
        {
            ret = sysProp.reinit(isOwner);
        }

        if (ret)
        {
            panda::core::PandaErrc res = mClient.connect(PROP_SRV_ADDR);

            if (res == panda::core::PandaErrc::OK)
            {
                mClient.subscribeSignal(PROP_SRV_SIG_NAME, [this](const std::string&, const IpcPacketData&)
                {
                    mCond.notify_all(); /** todo something */
                });
                mIsInitialized = true;
            }

            ret = res == panda::core::PandaErrc::OK;
        }
    }

    return ret;
}


bool PropertyImpl::set(const std::string& key, const std::string& val)
{
    if (mIsInitialized)
    {
        return sendProp(key, val);
    }

    return  false;
}

bool PropertyImpl::set(const std::string& key, int8_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, int16_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, int32_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, int64_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, uint8_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, uint16_t val)
{
    return set(key, std::to_string(val));
}


bool PropertyImpl::set(const std::string& key, uint32_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, uint64_t val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, float val)
{
    return set(key, std::to_string(val));
}

bool PropertyImpl::set(const std::string& key, double val)
{
    return set(key, std::to_string(val));
} 


bool PropertyImpl::get(const std::string& key, std::string& val)
{
    bool ret = true;
    SystemProperty& sysProp = SystemProperty::instance();
    if (!sysProp.isInited())
    {
        ret = sysProp.reinit(false);
    }

    if (ret)
    {
        panda::core::PandaErrc res = sysProp.get(key, val);

        ret = (res == panda::core::PandaErrc::OK);
    }

    return ret;
}

bool PropertyImpl::get(const std::string& key, int8_t& val)
{

    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = atoi(strVal.c_str());
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, int16_t& val)
{

    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = atoi(strVal.c_str());
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, int32_t& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = atoi(strVal.c_str());
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, int64_t& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = atoll(strVal.c_str());
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, uint8_t& val)
{
     std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = (uint8_t)strtoul(strVal.c_str(), nullptr, 10);
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, uint16_t& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = (uint16_t)strtoul(strVal.c_str(), nullptr, 10);
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, uint32_t& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = strtoul(strVal.c_str(), nullptr, 10);
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, uint64_t& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = strtoull(strVal.c_str(), nullptr, 10);
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, float& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = (float)atof(strVal.c_str());
            return true;
        }
    }

    return false;
}

bool PropertyImpl::get(const std::string& key, double& val)
{
    std::string strVal = "";

    if (get(key, strVal))
    {
        if (!strVal.empty())
        {
            val = atof(strVal.c_str());
            return true;
        }
    }

    return false;
}



std::string PropertyImpl::getOr(const std::string& key, const std::string& defVal)
{
    std::string val = "";

    if (get(key, val))
    {
        return std::move(val);
    }

    return defVal;
}

PROP_IMPL_GETOR(int8_t)
PROP_IMPL_GETOR(int16_t)
PROP_IMPL_GETOR(int32_t)
PROP_IMPL_GETOR(int64_t)
PROP_IMPL_GETOR(uint8_t)
PROP_IMPL_GETOR(uint16_t)
PROP_IMPL_GETOR(uint32_t)
PROP_IMPL_GETOR(uint64_t)
PROP_IMPL_GETOR(float)
PROP_IMPL_GETOR(double)


bool PropertyImpl::foreach(void (*propFunc)(const std::string& key, const std::string& val, void* opaque),
                           void* opaque)
{
    bool ret = true;
    SystemProperty& sysProp = SystemProperty::instance();
    if (!sysProp.isInited())
    {
        ret = sysProp.reinit(false);
    }

    if (ret)
    {
        struct opa
        {
            void* opaque;
            void (*propFunc)(const std::string& key, const std::string& val, void* opaque);
        };

        opa myOpaque;
        myOpaque.opaque = opaque;
        myOpaque.propFunc = propFunc;

        ret = sysProp.foreach([](const PropNode* pn, void* opaque)
            {
                if (pn && opaque)
                {
                    opa* myOp = (opa*)opaque;
                    myOp->propFunc(pn->name, (char*)pn->value, myOp->opaque);
                }
            }, &myOpaque);
    }

    return ret;
}

bool PropertyImpl::getPropertyList(PropertyList& proplist)
{
    bool ret = true;
    SystemProperty& sysProp = SystemProperty::instance();
    if (!sysProp.isInited())
    {
        ret = sysProp.reinit(false);
    }

    if (ret)
    {
        ret = sysProp.foreach([](const PropNode* pn, void* opaque)
                            {
                                if (pn && opaque)
                                {
                                    PropertyList& pl = *((PropertyList*)opaque);
                                    pl[pn->name] = (char*)pn->value;                    
                                }
                            }, &proplist);
    }

    return ret;
}

/** timeoutMs: timeout duration, -1: wait for ever, >= 0: wait duration */
bool PropertyImpl::waitForProperty(const std::string& key,
                                   const std::string& expectVal,
                                   int32_t timeoutMs /*= -1*/)
{
    bool ret = true;
    SystemProperty& sysProp = SystemProperty::instance();
    if (!sysProp.isInited())
    {
        ret = sysProp.reinit(false);
    }

    if (ret)
    {    
        bool waitForEver = (timeoutMs < 0); /** is need direct check == -1? */
        int64_t st = 0;

        int64_t endTime = 0;
        if (!waitForEver)
        {
            st = panda::core::NowMs();
            endTime = st + timeoutMs;
        }

        while (true)
        {
            const PropNode* pn = sysProp.find(key);

            if (pn)
            {
                if (((char*)pn->value) == expectVal)
                {
                    ret = true;
                    break;
                }
            }
            else
            {
                ret = false; /** if prop not found ,then return false */
                break;
            }

            if (waitForEver)
            {
                AutoLock al(mLock);
                mCond.wait(al);
            }
            else
            {
                int64_t waitTime = endTime - panda::core::NowMs();
                if (waitTime > 0)
                {
                    AutoLock al(mLock);
                    if (mCond.wait_for(al, std::chrono::milliseconds(waitTime)) == std::cv_status::timeout)
                    {
                        ret = false;
                        break;
                    }
                }
                else
                {
                    ret = false; /** time out */
                    break;
                }
            }

        }
    }

    return ret;
}

/** timeoutMs: timeout duration, -1: wait for ever, >= 0: wait duration */
bool PropertyImpl::waitForPropertyCreation(const std::string& key, int32_t timeoutMs /*= -1*/)
{
    bool ret = true;
    SystemProperty& sysProp = SystemProperty::instance();
    if (!sysProp.isInited())
    {
        ret = sysProp.reinit(false);
    }

    if (ret)
    {
        bool waitForEver = (timeoutMs < 0);  /** is need direct check == -1? */
        int64_t st = 0;

        int64_t endTime = 0;
        if (!waitForEver)
        {
            st = panda::core::NowMs();
            endTime = st + timeoutMs;
        }

        while (true)
        {
            const PropNode* pn = sysProp.find(key);

            if (pn)
            {
                ret = true;
                break;
            }

            if (waitForEver)
            {
                AutoLock al(mLock);
                mCond.wait(al);
            }
            else
            {
                int64_t waitTime = endTime - panda::core::NowMs();
                if (waitTime > 0)
                {
                    AutoLock al(mLock);
                    if (mCond.wait_for(al, std::chrono::milliseconds(waitTime)) == std::cv_status::timeout)
                    {
                        ret = false;
                        break;
                    }
                }
                else
                {
                    ret = false; /** time out */
                    break;
                }
            }
        }
    }

    return ret;
}

/**
 * wait until the property changed(don't care about change to what) or time out
 * timeoutMs: timeout duration, -1: wait for ever, >= 0: wait duration
 */
bool PropertyImpl::waitForPropertyChange(const std::string& key, 
                                         int32_t timeoutMs /*= -1*/)
{
    bool ret = true;
    SystemProperty& sysProp = SystemProperty::instance();
    if (!sysProp.isInited())
    {
        ret = sysProp.reinit(false);
    }

    if (ret)
    {     
        std::string curVal = "";
        panda::core::PandaErrc res = sysProp.get(key, curVal);

        if (res == panda::core::PandaErrc::OK)
        {
            bool waitForEver = (timeoutMs < 0); /** is need direct check == -1? */
            int64_t st = 0;

            int64_t endTime = 0;
            if (!waitForEver)
            {
                st = panda::core::NowMs();
                endTime = st + timeoutMs;
            }

            while (true)
            {
                const PropNode* pn = sysProp.find(key);

                if (pn)
                {
                    if (curVal != ((char*)pn->value))
                    {
                        ret = true;
                        break;
                    }
                }
                else
                {
                    ret = true; /** maybe not run to here forever */
                    break;
                }

                if (waitForEver)
                {
                    AutoLock al(mLock);
                    mCond.wait(al);
                }
                else
                {
                    int64_t waitTime = endTime - panda::core::NowMs();
                    if (waitTime > 0)
                    {
                        AutoLock al(mLock);
                        if (mCond.wait_for(al, std::chrono::milliseconds(waitTime)) == std::cv_status::timeout)
                        {
                            ret = false;
                            break;
                        }
                    }
                    else
                    {
                        ret = false; /** time out */
                        break;
                    }
                }
            }
        }       
    }

    return ret;
}


bool PropertyImpl::sendProp(const std::string& key, const std::string& val)
{
    panda::sys::Serializer ser;

    ser.write(key);
    ser.write(val);

    panda::core::PandaErrc ret = mClient.requestAsync(PROP_SRV_METHOD_SET, ser.payload(), ser.size(), nullptr);

    if (ret != panda::core::PandaErrc::OK)
    {
        LOGE("send property key-value to property_service failed: %s\n", panda::core::ErrorStr(ret));
    } 

    return ret == panda::core::PandaErrc::OK;
}

} /** namespace internal_impl */
} /** namespace sys */
} /** namespace panda */
